<!DOCTYPE html>
<html lang="en" dir="auto">

<head><meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="index, follow">
<title>DUSt3R and MUSt3R | WangJV Blog</title>
<meta name="keywords" content="深度学习, 计算机视觉, 三维重建">
<meta name="description" content="1. DUSt3R
1.1. Introduction
一般而言，现代的 MVS 和 SFM 的流程总是可以总结为以下几个子问题

特征点匹配
寻找本质矩阵
对点进行三角测量
对场景进行稀疏重建
估计相机参数，
密集重建

但是在这个复杂的过程中，每个子问题都对原始问题做了简化，无法完美解决，为后面的步骤引入了噪声，从而导致整个系统显的“精致而脆弱”。在这方面，每个子问题之间缺乏沟通就很能说明问题：如果能将这些缓解紧耦合到一起，将噪声统一的，全局的考虑，可以很大程度上解决应为过度简化和解耦导致的种种问题。此外，这个流程中的关键步骤很脆弱，在很多情况下容易出错。例如，很多 SFM 方法都依赖于相机参数的估计，但是如果遇到观察比较少、非漫反射表面或者相机姿态运动较为单一时，相机参数估计可能失效，导致整个 SFM 过程都会失效。归根结底：一个多视图立体视觉（MVS）算法的性能仅取决于输入图像和相机参数的质量
事实上，单张图或者多张图哦通过深度学习的方式提取深度并不罕有。但是在不引入额外的先验信息时，这个问题往往是不适定的，所以这些方法利用神经网络从大量数据中学习巨量的三维先验知识来解决模糊性问题。这些方法可以分为两类。第一类利用类别级别的物体先验知识，事实上 DreamFusion 就属于这类工作，可以从单张照片或者一句自然语言描述生成三纬结构。另一种与 DUSt3R 较为类似，系统的学习一般的场景来实现单目深度估计。但是一般而言，例如 SuperGlue 之类，在训练和推理过程中，都没有显然的引入三维结构的信息，也没有扔掉相机矩阵的桎梏。可以说，DUSt3R 是一种基于深度学习的 ALL in One 的深度估计方法，入了点图（Point Map）表示，使网络能够在规范框架中预测三维形状。
1.2. Method and forward
1.2.1. Ponit Map
接下来，我们将图片中每个像素对应的三维点构成的集合称为 点图（point map） $X \in R^{W×H×3}$。与分辨率为 $W×H$的对应RGB图像 $I$相关联，点图 $X$ 在图像像素与三维点之间形成一一映射，即对于所有像素坐标 $(i, j) \in \{1...W\}×\{1...H\}$，都有 $I_{i,j} \leftrightarrow X_{i,j}$。此处每个像素点对应于一个三维点实事丧引入了一个简化假设，即假设观测的场景全部是不透明且漫反射的，不存在透过某个物体并观察到另一个物体的情况。
1.2.2. 相机和场景
相机与场景。给定相机内参 $K \in \mathbb{R}^{3 ×3}$  ，所观测场景的点图 $X$ 可以直接从真实深度图：
$$
D \in \mathbb{R}^{W ×H}
$$中获取，其公式为
$$
\begin{aligned}
X_{i, j}
&= K^{-1}D_{i,j}[i , j , 1]^{\top}\\
\end{aligned}
$$其中， $X$ 是在相机坐标系中表示的。接下来，我们将在相机 $m$ 的坐标系中表示的、来自相机 $\pi$ 的点图 $X^{n}$ 记为 $X^{n, m}$，表示第 $n$ 帧在 第 $m$ 帧的坐标系下的点图为 ：">
<meta name="author" content="WangJV">
<link rel="canonical" href="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/">
<link crossorigin="anonymous" href="https://wangjv0812.github.io/WangJV-Blog-Pages/assets/css/stylesheet.8fe10233a706bc87f2e08b3cf97b8bd4c0a80f10675a143675d59212121037c0.css" integrity="sha256-j&#43;ECM6cGvIfy4Is8&#43;XuL1MCoDxBnWhQ2ddWSEhIQN8A=" rel="preload stylesheet" as="style">
<link rel="icon" href="https://wangjv0812.github.io/WangJV-Blog-Pages/favicon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="https://wangjv0812.github.io/WangJV-Blog-Pages/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="https://wangjv0812.github.io/WangJV-Blog-Pages/favicon-32x32.png">
<link rel="apple-touch-icon" href="https://wangjv0812.github.io/WangJV-Blog-Pages/apple-touch-icon.png">
<link rel="mask-icon" href="https://wangjv0812.github.io/WangJV-Blog-Pages/safari-pinned-tab.svg">
<meta name="theme-color" content="#2e2e33">
<meta name="msapplication-TileColor" content="#2e2e33">
<link rel="alternate" hreflang="en" href="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/">
<noscript>
    <style>
        #theme-toggle,
        .top-link {
            display: none;
        }

    </style>
    <style>
        @media (prefers-color-scheme: dark) {
            :root {
                --theme: rgb(29, 30, 32);
                --entry: rgb(46, 46, 51);
                --primary: rgb(218, 218, 219);
                --secondary: rgb(155, 156, 157);
                --tertiary: rgb(65, 66, 68);
                --content: rgb(196, 196, 197);
                --code-block-bg: rgb(46, 46, 51);
                --code-bg: rgb(55, 56, 62);
                --border: rgb(51, 51, 51);
            }

            .list {
                background: var(--theme);
            }

            .list:not(.dark)::-webkit-scrollbar-track {
                background: 0 0;
            }

            .list:not(.dark)::-webkit-scrollbar-thumb {
                border-color: var(--theme);
            }
        }

    </style>
</noscript><script>
  MathJax = {
    tex: {
      displayMath: [['\\[', '\\]'], ['$$', '$$']],
      inlineMath: [['\\(', '\\)'], ['$', '$']],
      processEscapes: true,
      processEnvironments: true,
      tags: 'ams'
    },
    chtml: {
      scale: 1,                     
      minScale: 0.5,               
      matchFontHeight: false,       
      displayAlign: 'center',       
      displayIndent: '0',           
      mtextInheritFont: false,      
      merrorInheritFont: true,      
      mathmlSpacing: false,         
      skipHtmlTags: ['script','noscript','style','textarea','pre','code','a'],
      ignoreHtmlClass: 'tex2jax_ignore',
      processHtmlClass: 'tex2jax_process'
    },
    svg: {
      scale: 1,                     
      minScale: 0.5,               
      mtextInheritFont: false,      
      merrorInheritFont: true,
      mathmlSpacing: false,
      skipHtmlTags: ['script','noscript','style','textarea','pre','code','a'],
      ignoreHtmlClass: 'tex2jax_ignore',
      processHtmlClass: 'tex2jax_process'
    },
    options: {
      enableMenu: true,             
      menuOptions: {
        settings: {
          zoom: 'Click'             
        }
      }
    },
    loader: {
      load: ['ui/safe', 'a11y/assistive-mml']
    },
    startup: {
      ready() {
        MathJax.startup.defaultReady();
        
        const observer = new ResizeObserver(entries => {
          MathJax.typesetPromise();
        });
        observer.observe(document.body);
      }
    }
  };
  
  
  if (window.innerWidth <= 768) {
    MathJax.chtml = MathJax.chtml || {};
    MathJax.chtml.scale = 0.9;  
  }
</script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>


<style>
   
  .MathJax {
    outline: 0;
  }
  
   
  @media (max-width: 768px) {
    .MathJax {
      font-size: 90% !important;
    }
    
     
    .MathJax_Display {
      overflow-x: auto;
      overflow-y: hidden;
      padding: 0 !important;
      margin: 1em 0 !important;
    }
    
     
    .MathJax_CHTML {
      line-height: 1.2 !important;
    }
  }
  
   
  mjx-container[jax="CHTML"][display="true"] {
    overflow-x: auto;
    overflow-y: hidden;
    padding: 1px 0;
  }
</style><meta property="og:url" content="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/">
  <meta property="og:site_name" content="WangJV Blog">
  <meta property="og:title" content="DUSt3R and MUSt3R">
  <meta property="og:description" content="1. DUSt3R 1.1. Introduction 一般而言，现代的 MVS 和 SFM 的流程总是可以总结为以下几个子问题
特征点匹配 寻找本质矩阵 对点进行三角测量 对场景进行稀疏重建 估计相机参数， 密集重建 但是在这个复杂的过程中，每个子问题都对原始问题做了简化，无法完美解决，为后面的步骤引入了噪声，从而导致整个系统显的“精致而脆弱”。在这方面，每个子问题之间缺乏沟通就很能说明问题：如果能将这些缓解紧耦合到一起，将噪声统一的，全局的考虑，可以很大程度上解决应为过度简化和解耦导致的种种问题。此外，这个流程中的关键步骤很脆弱，在很多情况下容易出错。例如，很多 SFM 方法都依赖于相机参数的估计，但是如果遇到观察比较少、非漫反射表面或者相机姿态运动较为单一时，相机参数估计可能失效，导致整个 SFM 过程都会失效。归根结底：一个多视图立体视觉（MVS）算法的性能仅取决于输入图像和相机参数的质量
事实上，单张图或者多张图哦通过深度学习的方式提取深度并不罕有。但是在不引入额外的先验信息时，这个问题往往是不适定的，所以这些方法利用神经网络从大量数据中学习巨量的三维先验知识来解决模糊性问题。这些方法可以分为两类。第一类利用类别级别的物体先验知识，事实上 DreamFusion 就属于这类工作，可以从单张照片或者一句自然语言描述生成三纬结构。另一种与 DUSt3R 较为类似，系统的学习一般的场景来实现单目深度估计。但是一般而言，例如 SuperGlue 之类，在训练和推理过程中，都没有显然的引入三维结构的信息，也没有扔掉相机矩阵的桎梏。可以说，DUSt3R 是一种基于深度学习的 ALL in One 的深度估计方法，入了点图（Point Map）表示，使网络能够在规范框架中预测三维形状。
1.2. Method and forward 1.2.1. Ponit Map 接下来，我们将图片中每个像素对应的三维点构成的集合称为 点图（point map） $X \in R^{W×H×3}$。与分辨率为 $W×H$的对应RGB图像 $I$相关联，点图 $X$ 在图像像素与三维点之间形成一一映射，即对于所有像素坐标 $(i, j) \in \{1...W\}×\{1...H\}$，都有 $I_{i,j} \leftrightarrow X_{i,j}$。此处每个像素点对应于一个三维点实事丧引入了一个简化假设，即假设观测的场景全部是不透明且漫反射的，不存在透过某个物体并观察到另一个物体的情况。
1.2.2. 相机和场景 相机与场景。给定相机内参 $K \in \mathbb{R}^{3 ×3}$ ，所观测场景的点图 $X$ 可以直接从真实深度图：
$$ D \in \mathbb{R}^{W ×H} $$中获取，其公式为
$$ \begin{aligned} X_{i, j} &amp;= K^{-1}D_{i,j}[i , j , 1]^{\top}\\ \end{aligned} $$其中， $X$ 是在相机坐标系中表示的。接下来，我们将在相机 $m$ 的坐标系中表示的、来自相机 $\pi$ 的点图 $X^{n}$ 记为 $X^{n, m}$，表示第 $n$ 帧在 第 $m$ 帧的坐标系下的点图为 ：">
  <meta property="og:locale" content="en-us">
  <meta property="og:type" content="article">
    <meta property="article:section" content="posts">
    <meta property="article:published_time" content="2025-03-10T16:40:25+08:00">
    <meta property="article:modified_time" content="2025-03-10T16:40:25+08:00">
    <meta property="article:tag" content="深度学习">
    <meta property="article:tag" content="计算机视觉">
    <meta property="article:tag" content="三维重建">
      <meta property="og:image" content="https://wangjv0812.github.io/WangJV-Blog-Pages/">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://wangjv0812.github.io/WangJV-Blog-Pages/">
<meta name="twitter:title" content="DUSt3R and MUSt3R">
<meta name="twitter:description" content="1. DUSt3R
1.1. Introduction
一般而言，现代的 MVS 和 SFM 的流程总是可以总结为以下几个子问题

特征点匹配
寻找本质矩阵
对点进行三角测量
对场景进行稀疏重建
估计相机参数，
密集重建

但是在这个复杂的过程中，每个子问题都对原始问题做了简化，无法完美解决，为后面的步骤引入了噪声，从而导致整个系统显的“精致而脆弱”。在这方面，每个子问题之间缺乏沟通就很能说明问题：如果能将这些缓解紧耦合到一起，将噪声统一的，全局的考虑，可以很大程度上解决应为过度简化和解耦导致的种种问题。此外，这个流程中的关键步骤很脆弱，在很多情况下容易出错。例如，很多 SFM 方法都依赖于相机参数的估计，但是如果遇到观察比较少、非漫反射表面或者相机姿态运动较为单一时，相机参数估计可能失效，导致整个 SFM 过程都会失效。归根结底：一个多视图立体视觉（MVS）算法的性能仅取决于输入图像和相机参数的质量
事实上，单张图或者多张图哦通过深度学习的方式提取深度并不罕有。但是在不引入额外的先验信息时，这个问题往往是不适定的，所以这些方法利用神经网络从大量数据中学习巨量的三维先验知识来解决模糊性问题。这些方法可以分为两类。第一类利用类别级别的物体先验知识，事实上 DreamFusion 就属于这类工作，可以从单张照片或者一句自然语言描述生成三纬结构。另一种与 DUSt3R 较为类似，系统的学习一般的场景来实现单目深度估计。但是一般而言，例如 SuperGlue 之类，在训练和推理过程中，都没有显然的引入三维结构的信息，也没有扔掉相机矩阵的桎梏。可以说，DUSt3R 是一种基于深度学习的 ALL in One 的深度估计方法，入了点图（Point Map）表示，使网络能够在规范框架中预测三维形状。
1.2. Method and forward
1.2.1. Ponit Map
接下来，我们将图片中每个像素对应的三维点构成的集合称为 点图（point map） $X \in R^{W×H×3}$。与分辨率为 $W×H$的对应RGB图像 $I$相关联，点图 $X$ 在图像像素与三维点之间形成一一映射，即对于所有像素坐标 $(i, j) \in \{1...W\}×\{1...H\}$，都有 $I_{i,j} \leftrightarrow X_{i,j}$。此处每个像素点对应于一个三维点实事丧引入了一个简化假设，即假设观测的场景全部是不透明且漫反射的，不存在透过某个物体并观察到另一个物体的情况。
1.2.2. 相机和场景
相机与场景。给定相机内参 $K \in \mathbb{R}^{3 ×3}$  ，所观测场景的点图 $X$ 可以直接从真实深度图：
$$
D \in \mathbb{R}^{W ×H}
$$中获取，其公式为
$$
\begin{aligned}
X_{i, j}
&= K^{-1}D_{i,j}[i , j , 1]^{\top}\\
\end{aligned}
$$其中， $X$ 是在相机坐标系中表示的。接下来，我们将在相机 $m$ 的坐标系中表示的、来自相机 $\pi$ 的点图 $X^{n}$ 记为 $X^{n, m}$，表示第 $n$ 帧在 第 $m$ 帧的坐标系下的点图为 ：">


<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position":  1 ,
      "name": "Posts",
      "item": "https://wangjv0812.github.io/WangJV-Blog-Pages/posts/"
    }, 
    {
      "@type": "ListItem",
      "position":  2 ,
      "name": "DUSt3R and MUSt3R",
      "item": "https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/"
    }
  ]
}
</script>
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "DUSt3R and MUSt3R",
  "name": "DUSt3R and MUSt3R",
  "description": "1. DUSt3R 1.1. Introduction 一般而言，现代的 MVS 和 SFM 的流程总是可以总结为以下几个子问题\n特征点匹配 寻找本质矩阵 对点进行三角测量 对场景进行稀疏重建 估计相机参数， 密集重建 但是在这个复杂的过程中，每个子问题都对原始问题做了简化，无法完美解决，为后面的步骤引入了噪声，从而导致整个系统显的“精致而脆弱”。在这方面，每个子问题之间缺乏沟通就很能说明问题：如果能将这些缓解紧耦合到一起，将噪声统一的，全局的考虑，可以很大程度上解决应为过度简化和解耦导致的种种问题。此外，这个流程中的关键步骤很脆弱，在很多情况下容易出错。例如，很多 SFM 方法都依赖于相机参数的估计，但是如果遇到观察比较少、非漫反射表面或者相机姿态运动较为单一时，相机参数估计可能失效，导致整个 SFM 过程都会失效。归根结底：一个多视图立体视觉（MVS）算法的性能仅取决于输入图像和相机参数的质量\n事实上，单张图或者多张图哦通过深度学习的方式提取深度并不罕有。但是在不引入额外的先验信息时，这个问题往往是不适定的，所以这些方法利用神经网络从大量数据中学习巨量的三维先验知识来解决模糊性问题。这些方法可以分为两类。第一类利用类别级别的物体先验知识，事实上 DreamFusion 就属于这类工作，可以从单张照片或者一句自然语言描述生成三纬结构。另一种与 DUSt3R 较为类似，系统的学习一般的场景来实现单目深度估计。但是一般而言，例如 SuperGlue 之类，在训练和推理过程中，都没有显然的引入三维结构的信息，也没有扔掉相机矩阵的桎梏。可以说，DUSt3R 是一种基于深度学习的 ALL in One 的深度估计方法，入了点图（Point Map）表示，使网络能够在规范框架中预测三维形状。\n1.2. Method and forward 1.2.1. Ponit Map 接下来，我们将图片中每个像素对应的三维点构成的集合称为 点图（point map） $X \\in R^{W×H×3}$。与分辨率为 $W×H$的对应RGB图像 $I$相关联，点图 $X$ 在图像像素与三维点之间形成一一映射，即对于所有像素坐标 $(i, j) \\in \\{1...W\\}×\\{1...H\\}$，都有 $I_{i,j} \\leftrightarrow X_{i,j}$。此处每个像素点对应于一个三维点实事丧引入了一个简化假设，即假设观测的场景全部是不透明且漫反射的，不存在透过某个物体并观察到另一个物体的情况。\n1.2.2. 相机和场景 相机与场景。给定相机内参 $K \\in \\mathbb{R}^{3 ×3}$ ，所观测场景的点图 $X$ 可以直接从真实深度图：\n$$ D \\in \\mathbb{R}^{W ×H} $$中获取，其公式为\n$$ \\begin{aligned} X_{i, j} \u0026= K^{-1}D_{i,j}[i , j , 1]^{\\top}\\\\ \\end{aligned} $$其中， $X$ 是在相机坐标系中表示的。接下来，我们将在相机 $m$ 的坐标系中表示的、来自相机 $\\pi$ 的点图 $X^{n}$ 记为 $X^{n, m}$，表示第 $n$ 帧在 第 $m$ 帧的坐标系下的点图为 ：\n",
  "keywords": [
    "深度学习", "计算机视觉", "三维重建"
  ],
  "articleBody": "1. DUSt3R 1.1. Introduction 一般而言，现代的 MVS 和 SFM 的流程总是可以总结为以下几个子问题\n特征点匹配 寻找本质矩阵 对点进行三角测量 对场景进行稀疏重建 估计相机参数， 密集重建 但是在这个复杂的过程中，每个子问题都对原始问题做了简化，无法完美解决，为后面的步骤引入了噪声，从而导致整个系统显的“精致而脆弱”。在这方面，每个子问题之间缺乏沟通就很能说明问题：如果能将这些缓解紧耦合到一起，将噪声统一的，全局的考虑，可以很大程度上解决应为过度简化和解耦导致的种种问题。此外，这个流程中的关键步骤很脆弱，在很多情况下容易出错。例如，很多 SFM 方法都依赖于相机参数的估计，但是如果遇到观察比较少、非漫反射表面或者相机姿态运动较为单一时，相机参数估计可能失效，导致整个 SFM 过程都会失效。归根结底：一个多视图立体视觉（MVS）算法的性能仅取决于输入图像和相机参数的质量\n事实上，单张图或者多张图哦通过深度学习的方式提取深度并不罕有。但是在不引入额外的先验信息时，这个问题往往是不适定的，所以这些方法利用神经网络从大量数据中学习巨量的三维先验知识来解决模糊性问题。这些方法可以分为两类。第一类利用类别级别的物体先验知识，事实上 DreamFusion 就属于这类工作，可以从单张照片或者一句自然语言描述生成三纬结构。另一种与 DUSt3R 较为类似，系统的学习一般的场景来实现单目深度估计。但是一般而言，例如 SuperGlue 之类，在训练和推理过程中，都没有显然的引入三维结构的信息，也没有扔掉相机矩阵的桎梏。可以说，DUSt3R 是一种基于深度学习的 ALL in One 的深度估计方法，入了点图（Point Map）表示，使网络能够在规范框架中预测三维形状。\n1.2. Method and forward 1.2.1. Ponit Map 接下来，我们将图片中每个像素对应的三维点构成的集合称为 点图（point map） $X \\in R^{W×H×3}$。与分辨率为 $W×H$的对应RGB图像 $I$相关联，点图 $X$ 在图像像素与三维点之间形成一一映射，即对于所有像素坐标 $(i, j) \\in \\{1...W\\}×\\{1...H\\}$，都有 $I_{i,j} \\leftrightarrow X_{i,j}$。此处每个像素点对应于一个三维点实事丧引入了一个简化假设，即假设观测的场景全部是不透明且漫反射的，不存在透过某个物体并观察到另一个物体的情况。\n1.2.2. 相机和场景 相机与场景。给定相机内参 $K \\in \\mathbb{R}^{3 ×3}$ ，所观测场景的点图 $X$ 可以直接从真实深度图：\n$$ D \\in \\mathbb{R}^{W ×H} $$中获取，其公式为\n$$ \\begin{aligned} X_{i, j} \u0026= K^{-1}D_{i,j}[i , j , 1]^{\\top}\\\\ \\end{aligned} $$其中， $X$ 是在相机坐标系中表示的。接下来，我们将在相机 $m$ 的坐标系中表示的、来自相机 $\\pi$ 的点图 $X^{n}$ 记为 $X^{n, m}$，表示第 $n$ 帧在 第 $m$ 帧的坐标系下的点图为 ：\n$$ X^{n, m}=P_{m} P_{n}^{-1} h\\left(X^{n}\\right) $$其中 $P_{m}$、$P_{n} \\in \\mathbb{R}^{3 ×4}$分别是图像$n$和图像$m$从世界坐标系到相机坐标系的位姿矩阵，$h:(x, y, z) \\to (x, y, z, 1)$ 是从笛卡尔坐标到齐次坐标的映射。\n1.2.3. 网络结构 DUSt3R 训练了一个用于预测三维场景点的回归网络 $\\bm F$。该神经网络的输入为两张 RGB 图像 $I_1, I_2 \\in \\mathbb{R}^{W×H×3}$，输出为两张图片对应的点图（point map）$X_{1,1}, X_{2,1} \\in \\mathbb{R}^{W×H×3}$。 这两张点图的坐标原点固定在 $I_1$ 的坐标原点上，与 $I_1$ 的相机坐标系重合。此外还会预测出对每个像素点的三维坐标预测的置信度 $C_{1,1}, C_{2,1} \\in \\mathbb{R}^{W×H}$。\n如上所示，它由两个相同的分支组成（每个图像对应一个分支），每个分支都包含一个图像编码器、一个解码器和一个回归头。首先，两张输入图像由同一个权重共享的Vision Transformer（ViT）编码器以孪生网络的方式进行编码，得到两个标记表示 $F^{1}$ 和 $F^{2}$：\n$$ \\begin{aligned} F^{1}=\\text{Encoder}(I^{1})\\\\ F^{2}=\\text{Encoder}(I^{2}) \\end{aligned} $$因此，每个解码器模块会按顺序执行自注意力机制，然后执行交叉注意力机制，最后将标记输入到 MLP 中。在 Decoder 中的交叉注意力机制是网络能实现点三维点重建的关键。\n$$ \\begin{aligned} G_{i}^{1} \u0026= \\text{DecoderBlock}_{i}^{1}(G_{i - 1}^{1}, G_{i - 1}^{2})\\\\ G_{i}^{2} \u0026= \\text{DecoderBlock}_{i}^{2}(G_{i - 1}^{2}, G_{i - 1}^{1}) \\end{aligned} $$对于有 $B$ 个模块的解码器， $i = 1, ..., B$ ，并使用编码器标记 $G_{0}^{1}:=F^{1}$ 和 $G_{0}^{2}:=F^{2}$ 进行初始化。这里， $\\text{DecoderBlock}_{i}^{v}(G^{1}, G^{2})$ 表示分支 $v \\in \\{1, 2\\}$ 中的第 $i$ 个模块， $G^{1}$ 和 $G^{2}$ 是输入标记，其中 $G^{2}$ 是来自另一个分支的标记。最后，在每个分支中，一个独立的回归头会获取解码器的标记集，并输出一个点图和一个相关的置信度图：\n$$ \\begin{aligned} X^{1,1}, C^{1,1} \u0026= \\text{Head}^{1}(G_{0}^{1}, ..., G_{B}^{1})\\\\ X^{2,1}, C^{2,1} \u0026= \\text{Head}^{2}(G_{0}^{2}, ..., G_{B}^{2}) \\end{aligned} $$1.3. Training 1.3.1. 三维回归损失 我们唯一的训练目标是基于三维空间中的回归。我们将真实点图记为 $\\overline{X}_{1,1}$ 和 $\\overline{X}_{2,1}$，它们是根据公式\n$$ X^{n, m}=P_{m} P_{n}^{-1} h\\left(X^{n}\\right) $$以及两组对应的有效像素点 $D_1, D_2 \\subseteq \\{1 \\ldots W\\} \\times \\{1 \\ldots H\\}$ 得到的，真实值是在这些有效像素点上定义的。对于视图 $v \\in \\{1, 2\\}$ 中有效像素点 $i \\in D_v$ 的回归损失，简单定义为欧几里得距离：\n$$ \\ell_{regr}(v, i) = \\left\\| \\frac{1}{z} X^{v,1}_i - \\frac{1}{\\overline{z}} \\overline{X}^{v,1}_i \\right\\| $$需要着重强调一下 $X^{v,1}$ 两个上标的含义是什么。如前式所示，点图中的点 $X$ 的第一个上标表示图像 $v$ 在图像 $1$ (实质上就是网络预测输入的第一张照片，就是 DUSt3R 正常的坐标系和输出顺序) 上的坐标。因此此处需要做的坐标变换主要是将图像 2 的 GT 变换到 图像 1 上，之后和图像 2 预测出的点图求欧式距离。\n为了解决预测值和真实值之间的尺度模糊问题，我们分别通过缩放因子 $z = \\text{norm}(X_{1,1}, X_{2,1})$ 和 $\\overline{z} = \\text{norm}(\\overline{X}_{1,1}, \\overline{X}_{2,1})$ 对预测点图和真实点图进行归一化处理，这些缩放因子简单表示为所有有效点到原点的平均距离：\n$$ \\text{norm}(X_1, X_2) = \\frac{1}{|D_1| + |D_2|} \\sum_{v \\in \\{1,2\\}} \\sum_{i \\in D_v} \\|X_v^i\\| $$这个归一化系数实质上是分别对预测的点图和Ground Truth 分别求了点到坐标原点的欧式距离的平均值。以此解决了预测值和真实值之间的尺度模糊问题。也就是说，DUSt3R 通过对点图进行归一化来解决尺度模糊的问题\n1.3.2. 置信损失 置信度感知损失。实际上，与我们的假设相反，存在定义不明确的三维点，例如在天空中或半透明物体上的点。更普遍地说，图像中的某些部分通常比其他部分更难预测。因此，我们联合学习为每个像素预测一个分数，该分数表示网络对该特定像素的置信度。最终的训练目标是对所有有效像素根据公式\n$$ \\ell_{regr}(v, i) = \\left\\| \\frac{1}{z} X_{v,1}^i - \\frac{1}{\\overline{z}} \\overline{X}_{v,1}^i \\right\\| $$计算的置信度加权回归损失：\n$$ L_{conf} = \\sum_{v \\in \\{1,2\\}} \\sum_{i \\in D_v} C_{v,1}^i \\ell_{regr}(v, i) - \\alpha \\log C_{v,1}^i $$其中 $C_{v,1}^i$ 是像素 $i$ 的置信度分数， $\\alpha$ 是一个控制正则化项的超参数。\n为了确保置信度严格为正，我们通常定义 $C_{v,1}^i = \\frac{1}{1 + \\exp(-g_{C_{v,1}^i})} \u003e 0$ （。这会迫使网络在更难处理的区域进行推断，例如那些仅由单个视图覆盖的区域。使用这个目标来训练网络 $F$ 可以在没有明确监督的情况下估计置信度分数。\n1.4. 应用 1.4.1. 三维点匹配 在三维点图空间中，通过最近邻搜索可以很容易地在两张图像的像素之间建立对应关系。为了将误差降至最低，我们通常会保留图像 $I^{1}$ 和 $I^{2}$ 之间的相互（双向）对应关系 $M_{1,2}$，也就是说，我们有：\n$$ \\begin{array}{c} \\mathcal{M}_{1,2}=\\left\\{(i, j) | i=NN_{1}^{1,2}(j)\\right. and \\left.j=NN_{1}^{2,1}(i)\\right\\}\\\\ \\text{where: } NN_{k}^{n, m}(i)=\\underset{j \\in\\{0, ..., W H\\}}{arg min }\\left\\| X_{j}^{n, k}-X_{i}^{m, k}\\right\\| \\end{array} $$1.4.2. 恢复相机内参 恢复相机内参。根据定义，点图 $X^{1,1}$ 是在图像 $I^{1}$ 的坐标系中表示的。因此，通过求解一个简单的优化问题来估计相机内参是可行的。在这项工作中，我们假设主点大致位于中心位置，且像素为正方形，因此仅需估计焦距 $f_{1}^{*}$ ：\n$$ f_{1}^{*}=\\underset{f_{1}}{\\arg\\min} \\sum_{i = 0}^{W} \\sum_{j = 0}^{H} C_{i, j}^{1,1}\\left\\| \\left(i', j'\\right)-f_{1} \\frac{\\left(X_{i, j, 0}^{1,1}, X_{i, j, 1}^{1,1}\\right)}{X_{i, j, 2}^{1,1}}\\right\\| $$其中：\n$$ X_{i, j}^{1,1} = \\left[\\begin{array}{ccc} X_{i, j, 0}^{1,1} \u0026 X_{i, j, 1}^{1,1} \u0026 X_{i, j, 2}^{1,1} \\end{array}\\right] ^\\top $$且有 $i' = i - \\frac{W}{2}$ 且 $j' = j - \\frac{H}{2}$。\n1.4.3. 相对位置姿估计 相对位姿估计可以通过多种方式实现。一种方法是如上述进行二维匹配并恢复内参，然后估计基础矩阵并恢复相对位姿。或者可以直接比较点图 $X^{1,1} \\leftrightarrow X^{1,2}$ 以得到相对位姿 $P^{*}=[R^{*} | t^{*}]$ ：\n$$ R^{*}, t^{*}=\\underset{\\sigma, R, t}{\\arg\\min} \\sum_{i} C_{i}^{1,1} C_{i}^{1,2}\\left\\| \\sigma\\left(R X_{i}^{1,1}+t\\right)-X_{i}^{1,2}\\right\\| ^{2}, $$这可以通过解析解得到。但是解析解对对噪声和外点十分敏感。可以考虑使用 RANSAC 求解。\n2. MASt3R MASt3R 和 DUSt3R 其实没有特别大的区别，事实上，MASt3R 和 DUSt3R 甚至没有太多新的代码。和 DUSt3R 相比，其主要增强了输入照片之间的特征匹配能力。具体而言，MASt3R 的 Encoder 和 Decoder 与 DUSt3R 是完全相同的，主要区别在于增加了一个预测头，用于预测出稠密的，逐像素的特征描述子 $F \\in R^{W \\times H \\times d}$，其中 $d$ 是特征描述子的维度。\n应为网络结构和三维结构预测头和 DUSt3R 是完全相同的，我们暂且不论，主要关注于 Local Feather Predicting Head 的训结构和训练。MASt3R 论文中提到，直接通过 DUSt3R 到的对应关系相当不精确，导致精度不够理想。其原因如下：\n回归本质上会受到噪声的影响 因为 DUSt3R 从未经过专门针对匹配的显式训练。 2.1. 匹配头 匹配头。出于这些原因，我们提议添加第二个头，它输出两个维度为 $d$的密集特征图 $D^{1}$和 $D^{2}$， $D^{1},D^{2}\\in \\mathbb{R}^{H ×W ×d}$：\n$$ \\begin{aligned} D^{1} \u0026= Head_{desc }^{1}\\left(\\left[H^{1}, H^{\\prime 1}\\right]\\right)\\\\ D^{2} \u0026= Head_{desc }^{2}\\left(\\left[H^{2}, H^{\\prime 2}\\right]\\right) \\end{aligned} $$ （其中 $Head_{desc }^{1}$和 $Head_{desc }^{2}$ 为特征描述子预测头， $[H^{1}, H^{\\prime 1}]$和 $[H^{2}, H^{\\prime 2}]$为相应的输入组合，上式用于计算两个特征图 $D^{1}$和 $D^{2}$。我们将这个头实现为一个简单的两层多层感知器（MLP），其中穿插了非线性 GELU 激活函数。最后，我们将每个局部特征归一化为单位范数。\n2.2. 匹配 loss 我们希望一张照片中的局部描述子最多与另一张照片中最多一个描述子相匹配，并且相互匹配的描述子描述了两张照片中同一个三维点。不妨假设我们有真值的匹配关系：\n$$ \\hat {\\mathcal M} = \\{(i, j) \\mid \\hat X_{i}^{1,1} = \\hat X_{i}^{1,2}\\} $$可以通过 infoNCE loss 来寻找描述子之间的对应关系：\n$$ \\mathcal{L}_{match }=-\\sum_{(i, j) \\in \\hat{\\mathcal{M}}} log \\frac{s_{\\tau}(i, j)}{\\sum_{k \\in \\mathcal{P}^{1}} s_{\\tau}(k, j)}+log \\frac{s_{\\tau}(i, j)}{\\sum_{k \\in \\mathcal{P}^{2}} s_{\\tau}(i, k)}, $$其中：\n$$ s_{\\tau}(i, j)=exp \\left[-\\tau D_{i}^{1 \\top} D_{j}^{2}\\right] $$在这里， $\\mathcal P^{1}=\\{i |(i, j) \\in \\hat{M}\\}$和 $\\mathcal P^{2}=\\{j |(i, j) \\in \\hat{M}\\}$分别表示每张图像中所考虑像素的子集，而 $\\tau$是一个温度超参数。请注意，这个匹配目标本质上是一种交叉熵分类损失：与 DUSt3R 的 3D 点回归损失不同，只有当网络正确匹配到正确的像素时，它才会得到奖励。这极大地促使网络实现高精度匹配。最终的损失为：\n$$ \\mathcal{L}_{total }=\\mathcal{L}_{conf }+\\beta \\mathcal{L}_{match } $$2.3. 快速相互匹配 (Fast reciprocal matching) 给定两个预测的特征图 $D^{1}, D^{2} \\in \\mathbb{R}^{H×W×d}$，我们希望可以在两张图片之间提取出对应的像素匹配关系：\n$$ \\mathcal{M}=\\{(i, j) | j = NN_{2}(D_{i}^{1}) \\text{ 且 } i = NN_{1}(D_{j}^{2})\\} $$其中：\n$$ NN_{A}(D_{j}^{B}) = \\underset{i}{\\arg\\min} \\| D_{i}^{A} - D_{j}^{B}\\| $$但是问题在于，我们目前面对的是一个稠密匹配问题。如果直接使用简单的暴力匹配，其时间复杂度将达到 $O(W^2H^2)$，着显然是无法接受的。当然，还可以用 k-d tree 一类的方法。但是事实上这类方法在特征的纬度非常高时时间复杂度也同样无法接受。因此，本文提出了一种名为 fast match 的方法，用于解决由于稠密匹配且特征维度过高导致的问题。\n这个方法的核心思路是通过采样，降低每一次匹配过程的时间复杂度。这个过程是一个迭代过程，每一次迭代可以分为下面几步：\n我们现在特征图 $D^1$ 中进行随机采样，稀疏的得到 $k$ 个像素 $U^{0}=\\{U_{n}^{0}\\}_{n = 1}^{k}$。 通过 NN，在 $D^2$ 上找到 $U^{0}=\\{U_{n}^{0}\\}_{n = 1}^{k}$ 对应的 $k$ 个像素 $V^{1}=\\{V_{n}^{1}\\}_{n = 1}^{k}$。 将 $D^2$ 上找到的特征 $V^{1}=\\{V_{n}^{1}\\}_{n = 1}^{k}$ 重新通过 NN 在 $D^1$ 上寻找匹配点 $U^{1}=\\{U_{n}^{1}\\}_{n = 1}^{k}$。 $$ \\begin{array}{c} U^{t} \\stackrel{}{\\longrightarrow}\\left[NN_{2}\\left(D_{u}^{1}\\right)\\right]_{u \\in U^{t}} = V^{t}\\\\ V^{t} \\stackrel{}{\\longrightarrow}\\left[NN_{1}\\left(D_{v}^{2}\\right)\\right]_{v \\in V^{t}} = U^{t + 1} \\end{array} $$ 对比 $U^{0}=\\{U_{n}^{0}\\}_{n = 1}^{k}$ 和 $U^{1}=\\{U_{n}^{1}\\}_{n = 1}^{k}$。我们认为满足 $M_{k}^{t}=\\{(U_{n}^{t}, V_{n}^{t}) | U_{n}^{t}=U_{n}^{t + 1}\\}$ 的点已经完成匹配，会将这些点过滤掉。再进行下一轮循环。 如上图所示，实质上，对于初试选择的 $k$ 个点，很快的会辗转的匹配，直到完全匹配。\n2.4. 从粗到细的匹配 事实上，自从 Transformer 提出的那一天开始，就因其长序列输入时的时间复杂度而被诟病。例如对于一个分辨率为 $(H, W)$ 的图像输入而言，如果将其划分为 $m$ 个 ViT 的图片序列，其时间复杂度为 $m^2$。因此 MASt3R 目前仅能处理最大为 512 像素的图像。事实上，高分辨率的图片往往首先需要降采样到 MASt3R 所能接受的最大水平，并将推理的结果上采样到原始分辨率。但是往往会导致性能损失和重建质量大幅下降。MASt3R 使用了从粗到细的匹配方法来解决这个问题。具体思路如下：\n对两张照片的降采样版本进行匹配。我们将子采样 $k$ 得到的粗匹配的对应关系记作 $M_{k}^{0}$ 接下来在每幅全分辨率图像上独立生成一组有重叠的裁剪窗口 $W \\in \\mathbb{R}^{w×4}$。每个窗口都有 $512$ 像素，且重叠率为 $50\\%$ 枚举所有可能的窗口对 $(w_1, w_2) \\in (W \\times W)$。之后选择一个覆盖粗匹配 $M_{k}^{0}$ 的子匹配 具体来说，我们以贪心的方式逐个添加窗口对，直到覆盖90%的对应关系。最后，我们对每个窗口对独立进行匹配： $$ \\begin{aligned} D^{\\omega_{1}}, D^{\\omega_{2}} \u0026= \\text{MASt3R}\\left(I_{w_{1}}^{1}, I_{w_{2}}^{2}\\right)\\\\ \\mathcal{M}_{k}^{w_{1}, w_{2}} \u0026= \\text{Fast\\_Reciprocal\\_Match} \\left(D^{w_{1}}, D^{w_{2}}\\right) \\end{aligned} $$\nReference @inproceedings{dust3r_cvpr24, title={DUSt3R: Geometric 3D Vision Made Easy}, author={Shuzhe Wang and Vincent Leroy and Yohann Cabon and Boris Chidlovskii and Jerome Revaud}, booktitle = {CVPR}, year = {2024} }\n@misc{mast3r_arxiv24, title={Grounding Image Matching in 3D with MASt3R}, author={Vincent Leroy and Yohann Cabon and Jerome Revaud}, year={2024}, eprint={2406.09756}, archivePrefix={arXiv}, primaryClass={cs.CV} }\n@inproceedings{croco, title={{CroCo: Self-Supervised Pre-training for 3D Vision Tasks by Cross-View Completion}}, author={{Weinzaepfel, Philippe and Leroy, Vincent and Lucas, Thomas and Br'egier, Romain and Cabon, Yohann and Arora, Vaibhav and Antsfeld, Leonid and Chidlovskii, Boris and Csurka, Gabriela and Revaud J'er^ome}}, booktitle={{NeurIPS}}, year={2022} }\n从DUSt3R 到 MASt3R\n",
  "wordCount" : "903",
  "inLanguage": "en",
  "image": "https://wangjv0812.github.io/WangJV-Blog-Pages/","datePublished": "2025-03-10T16:40:25+08:00",
  "dateModified": "2025-03-10T16:40:25+08:00",
  "author":{
    "@type": "Person",
    "name": "WangJV"
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/"
  },
  "publisher": {
    "@type": "Organization",
    "name": "WangJV Blog",
    "logo": {
      "@type": "ImageObject",
      "url": "https://wangjv0812.github.io/WangJV-Blog-Pages/favicon.ico"
    }
  }
}
</script>
</head>

<body class="" id="top">
<script>
    if (localStorage.getItem("pref-theme") === "dark") {
        document.body.classList.add('dark');
    } else if (localStorage.getItem("pref-theme") === "light") {
        document.body.classList.remove('dark')
    } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
        document.body.classList.add('dark');
    }

</script>

<header class="header">
    <nav class="nav">
        <div class="logo">
            <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/" accesskey="h" title="WangJV Blog (Alt + H)">WangJV Blog</a>
            <div class="logo-switches">
                <button id="theme-toggle" accesskey="t" title="(Alt + T)" aria-label="Toggle theme">
                    <svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
                        fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                        stroke-linejoin="round">
                        <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
                    </svg>
                    <svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
                        fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                        stroke-linejoin="round">
                        <circle cx="12" cy="12" r="5"></circle>
                        <line x1="12" y1="1" x2="12" y2="3"></line>
                        <line x1="12" y1="21" x2="12" y2="23"></line>
                        <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
                        <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
                        <line x1="1" y1="12" x2="3" y2="12"></line>
                        <line x1="21" y1="12" x2="23" y2="12"></line>
                        <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
                        <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
                    </svg>
                </button>
            </div>
        </div>
        <ul id="menu">
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/" title="Home">
                    <span>Home</span>
                </a>
            </li>
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/posts/" title="Posts">
                    <span>Posts</span>
                </a>
            </li>
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/archives/" title="Archive">
                    <span>Archive</span>
                </a>
            </li>
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/" title="Tags">
                    <span>Tags</span>
                </a>
            </li>
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/search/" title="🔍 Search (Alt &#43; /)" accesskey=/>
                    <span>🔍 Search</span>
                </a>
            </li>
        </ul>
    </nav>
</header>
<main class="main">

<article class="post-single">
  <header class="post-header">
    <div class="breadcrumbs"><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/">Home</a>&nbsp;»&nbsp;<a href="https://wangjv0812.github.io/WangJV-Blog-Pages/posts/">Posts</a></div>
    <h1 class="post-title entry-hint-parent">
      DUSt3R and MUSt3R
    </h1>
    <div class="post-meta"><span title='2025-03-10 16:40:25 +0800 CST'>March 10, 2025</span>&nbsp;·&nbsp;5 min&nbsp;·&nbsp;903 words&nbsp;·&nbsp;WangJV&nbsp;|&nbsp;<a href="https://github.com/WangJV0812/WangJV-Blog-Source/tree/master/content/posts/DUSt3R%20and%20MUSt3R/index.md" rel="noopener noreferrer edit" target="_blank">Suggest Changes</a>

</div>
  </header> <div class="toc">
    <details >
        <summary accesskey="c" title="(Alt + C)">
            <span class="details">Table of Contents</span>
        </summary>

        <div class="inner"><nav id="TableOfContents">
  <ul>
    <li><a href="#1-dust3r">1. DUSt3R</a>
      <ul>
        <li><a href="#11-introduction">1.1. Introduction</a></li>
        <li><a href="#12-method-and-forward">1.2. Method and forward</a></li>
        <li><a href="#13-training">1.3. Training</a></li>
        <li><a href="#14-应用">1.4. 应用</a></li>
      </ul>
    </li>
    <li><a href="#2-mast3r">2. MASt3R</a>
      <ul>
        <li><a href="#21-匹配头">2.1. 匹配头</a></li>
        <li><a href="#22-匹配-loss">2.2. 匹配 loss</a></li>
        <li><a href="#23-快速相互匹配-fast-reciprocal-matching">2.3. 快速相互匹配 (Fast reciprocal matching)</a></li>
        <li><a href="#24-从粗到细的匹配">2.4. 从粗到细的匹配</a></li>
      </ul>
    </li>
    <li><a href="#reference">Reference</a></li>
  </ul>
</nav>
        </div>
    </details>
</div>

  <div class="post-content"><h2 id="1-dust3r">1. DUSt3R<a hidden class="anchor" aria-hidden="true" href="#1-dust3r">#</a></h2>
<h3 id="11-introduction">1.1. Introduction<a hidden class="anchor" aria-hidden="true" href="#11-introduction">#</a></h3>
<p>一般而言，现代的 MVS 和 SFM 的流程总是可以总结为以下几个子问题</p>
<ul>
<li>特征点匹配</li>
<li>寻找本质矩阵</li>
<li>对点进行三角测量</li>
<li>对场景进行稀疏重建</li>
<li>估计相机参数，</li>
<li>密集重建</li>
</ul>
<p>但是在这个复杂的过程中，每个子问题都对原始问题做了简化，无法完美解决，为后面的步骤引入了噪声，从而导致整个系统显的“精致而脆弱”。在这方面，每个子问题之间缺乏沟通就很能说明问题：如果能将这些缓解紧耦合到一起，将噪声统一的，全局的考虑，可以很大程度上解决应为过度简化和解耦导致的种种问题。此外，这个流程中的关键步骤很脆弱，在很多情况下容易出错。例如，很多 SFM 方法都依赖于相机参数的估计，但是如果遇到观察比较少、非漫反射表面或者相机姿态运动较为单一时，相机参数估计可能失效，导致整个 SFM 过程都会失效。归根结底：<strong>一个多视图立体视觉（MVS）算法的性能仅取决于输入图像和相机参数的质量</strong></p>
<p>事实上，单张图或者多张图哦通过深度学习的方式提取深度并不罕有。但是在不引入额外的先验信息时，这个问题往往是<strong>不适定的</strong>，所以这些方法利用神经网络从大量数据中学习巨量的三维先验知识来解决模糊性问题。这些方法可以分为两类。第一类利用类别级别的物体先验知识，事实上 DreamFusion 就属于这类工作，可以从单张照片或者一句自然语言描述生成三纬结构。另一种与 DUSt3R 较为类似，系统的学习一般的场景来实现单目深度估计。但是一般而言，例如 SuperGlue 之类，在训练和推理过程中，都没有显然的引入三维结构的信息，也没有扔掉相机矩阵的桎梏。可以说，DUSt3R 是一种基于深度学习的 ALL in One 的深度估计方法，入了点图（Point Map）表示，使网络能够在规范框架中预测三维形状。</p>
<h3 id="12-method-and-forward">1.2. Method and forward<a hidden class="anchor" aria-hidden="true" href="#12-method-and-forward">#</a></h3>
<h4 id="121-ponit-map">1.2.1. Ponit Map<a hidden class="anchor" aria-hidden="true" href="#121-ponit-map">#</a></h4>
<p>接下来，我们将图片中每个像素对应的三维点构成的集合称为 <strong>点图</strong>（point map） $X \in R^{W×H×3}$。与分辨率为 $W×H$的对应RGB图像 $I$相关联，<strong>点图 $X$ 在图像像素与三维点之间形成一一映射</strong>，即对于所有像素坐标 $(i, j) \in \{1...W\}×\{1...H\}$，都有 $I_{i,j} \leftrightarrow X_{i,j}$。此处每个像素点对应于一个三维点实事丧引入了一个简化假设，即假设观测的场景全部是不透明且漫反射的，不存在透过某个物体并观察到另一个物体的情况。</p>
<h4 id="122-相机和场景">1.2.2. 相机和场景<a hidden class="anchor" aria-hidden="true" href="#122-相机和场景">#</a></h4>
<p>相机与场景。给定相机内参 $K \in \mathbb{R}^{3 ×3}$  ，所观测场景的点图 $X$ 可以直接从真实深度图：</p>
$$
D \in \mathbb{R}^{W ×H}
$$<p>中获取，其公式为</p>
$$
\begin{aligned}
X_{i, j}
&= K^{-1}D_{i,j}[i , j , 1]^{\top}\\
\end{aligned}
$$<p>其中， $X$ 是在相机坐标系中表示的。接下来，我们将在相机 $m$ 的坐标系中表示的、来自相机 $\pi$ 的点图 $X^{n}$ 记为 $X^{n, m}$，表示第 $n$ 帧在 第 $m$ 帧的坐标系下的点图为 ：</p>
$$
X^{n, m}=P_{m} P_{n}^{-1} h\left(X^{n}\right)
$$<p>其中 $P_{m}$、$P_{n} \in \mathbb{R}^{3 ×4}$分别是图像$n$和图像$m$从世界坐标系到相机坐标系的位姿矩阵，$h:(x, y, z) \to (x, y, z, 1)$ 是从笛卡尔坐标到齐次坐标的映射。</p>
<h4 id="123-网络结构">1.2.3. 网络结构<a hidden class="anchor" aria-hidden="true" href="#123-网络结构">#</a></h4>
<p><img alt="DUSt3R pipline" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/Images/DUSt3R%20pipline.png"></p>
<p>DUSt3R 训练了一个用于预测三维场景点的回归网络 $\bm F$。该神经网络的输入为两张 RGB 图像 $I_1, I_2 \in \mathbb{R}^{W×H×3}$，输出为两张图片对应的点图（point map）$X_{1,1}, X_{2,1} \in \mathbb{R}^{W×H×3}$。 这两张点图的坐标原点固定在 $I_1$ 的坐标原点上，与 $I_1$ 的相机坐标系重合。此外还会预测出对每个像素点的三维坐标预测的置信度 $C_{1,1}, C_{2,1} \in \mathbb{R}^{W×H}$。</p>
<p>如上所示，它由两个相同的分支组成（每个图像对应一个分支），每个分支都包含一个图像编码器、一个解码器和一个回归头。首先，两张输入图像由同一个权重共享的Vision Transformer（ViT）编码器以孪生网络的方式进行编码，得到两个标记表示  $F^{1}$ 和 $F^{2}$：</p>
$$
\begin{aligned}
F^{1}=\text{Encoder}(I^{1})\\
F^{2}=\text{Encoder}(I^{2})
\end{aligned}
$$<p>因此，每个解码器模块会按顺序执行自注意力机制，然后执行交叉注意力机制，最后将标记输入到 MLP 中。在 Decoder 中的交叉注意力机制是网络能实现点三维点重建的关键。</p>
$$
\begin{aligned}
  G_{i}^{1} &= \text{DecoderBlock}_{i}^{1}(G_{i - 1}^{1}, G_{i - 1}^{2})\\
  G_{i}^{2} &= \text{DecoderBlock}_{i}^{2}(G_{i - 1}^{2}, G_{i - 1}^{1})
\end{aligned}
$$<p>对于有 $B$ 个模块的解码器， $i = 1, ..., B$ ，并使用编码器标记 $G_{0}^{1}:=F^{1}$ 和 $G_{0}^{2}:=F^{2}$ 进行初始化。这里， $\text{DecoderBlock}_{i}^{v}(G^{1}, G^{2})$ 表示分支 $v \in \{1, 2\}$ 中的第 $i$ 个模块， $G^{1}$ 和 $G^{2}$ 是输入标记，其中 $G^{2}$ 是来自另一个分支的标记。最后，在每个分支中，一个独立的回归头会获取解码器的标记集，并输出一个点图和一个相关的置信度图：</p>
$$
\begin{aligned}
  X^{1,1}, C^{1,1} &= \text{Head}^{1}(G_{0}^{1}, ..., G_{B}^{1})\\
  X^{2,1}, C^{2,1} &= \text{Head}^{2}(G_{0}^{2}, ..., G_{B}^{2})
\end{aligned}
$$<h3 id="13-training">1.3. Training<a hidden class="anchor" aria-hidden="true" href="#13-training">#</a></h3>
<h4 id="131-三维回归损失">1.3.1. 三维回归损失<a hidden class="anchor" aria-hidden="true" href="#131-三维回归损失">#</a></h4>
<p>我们唯一的训练目标是基于三维空间中的回归。我们将真实点图记为 $\overline{X}_{1,1}$ 和 $\overline{X}_{2,1}$，它们是根据公式</p>
$$
X^{n, m}=P_{m} P_{n}^{-1} h\left(X^{n}\right)
$$<p>以及两组对应的有效像素点 $D_1, D_2 \subseteq \{1 \ldots W\} \times \{1 \ldots H\}$ 得到的，真实值是在这些有效像素点上定义的。对于视图 $v \in \{1, 2\}$ 中有效像素点 $i \in D_v$ 的回归损失，简单定义为欧几里得距离：</p>
$$
\ell_{regr}(v, i) = \left\|
\frac{1}{z} X^{v,1}_i - \frac{1}{\overline{z}} \overline{X}^{v,1}_i
\right\|
$$<p>需要着重强调一下 $X^{v,1}$ 两个上标的含义是什么。如前式所示，点图中的点 $X$ 的第一个上标表示图像 $v$ 在图像 $1$ (实质上就是网络预测输入的第一张照片，就是 DUSt3R 正常的坐标系和输出顺序) 上的坐标。因此此处需要做的坐标变换主要是将图像 2 的 GT 变换到 图像 1 上，之后和图像 2 预测出的点图求欧式距离。</p>
<p>为了解决预测值和真实值之间的尺度模糊问题，我们分别通过缩放因子 $z = \text{norm}(X_{1,1}, X_{2,1})$ 和 $\overline{z} = \text{norm}(\overline{X}_{1,1}, \overline{X}_{2,1})$ 对预测点图和真实点图进行归一化处理，这些缩放因子简单表示为所有有效点到原点的平均距离：</p>
$$
\text{norm}(X_1, X_2) = \frac{1}{|D_1| + |D_2|} \sum_{v \in \{1,2\}} \sum_{i \in D_v} \|X_v^i\|
$$<p>这个归一化系数实质上是分别对<strong>预测的点图</strong>和<strong>Ground Truth</strong> 分别求了<strong>点到坐标原点的欧式距离的平均值</strong>。以此解决了预测值和真实值之间的尺度模糊问题。也就是说，DUSt3R 通过对点图进行归一化来解决尺度模糊的问题</p>
<h4 id="132-置信损失">1.3.2. 置信损失<a hidden class="anchor" aria-hidden="true" href="#132-置信损失">#</a></h4>
<p>置信度感知损失。实际上，与我们的假设相反，存在定义不明确的三维点，例如在天空中或半透明物体上的点。更普遍地说，图像中的某些部分通常比其他部分更难预测。因此，我们联合学习为每个像素预测一个分数，该分数表示网络对该特定像素的置信度。最终的训练目标是对所有有效像素根据公式</p>
$$
\ell_{regr}(v, i) = \left\|
\frac{1}{z} X_{v,1}^i - \frac{1}{\overline{z}} \overline{X}_{v,1}^i
\right\|
$$<p>计算的置信度加权回归损失：</p>
$$
L_{conf} = \sum_{v \in \{1,2\}} \sum_{i \in D_v} C_{v,1}^i \ell_{regr}(v, i) - \alpha \log C_{v,1}^i
$$<p>其中 $C_{v,1}^i$ 是像素 $i$ 的置信度分数， $\alpha$ 是一个控制正则化项的超参数。</p>
<p>为了确保置信度严格为正，我们通常定义 $C_{v,1}^i = \frac{1}{1 + \exp(-g_{C_{v,1}^i})} > 0$  （。这会迫使网络在更难处理的区域进行推断，例如那些仅由单个视图覆盖的区域。使用这个目标来训练网络 $F$ 可以在没有明确监督的情况下估计置信度分数。</p>
<p><img alt="from left to right is RGB, depth map, confidence map" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/Images/DUSt3RPointmapAndConfidence.png"></p>
<p><img alt="Examples of 3DReconstructions From Nearly Opposite Viewpoints" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/Images/Examplesof3DReconstructionsFromNearlyOppositeViewpoints.png"></p>
<h3 id="14-应用">1.4. 应用<a hidden class="anchor" aria-hidden="true" href="#14-应用">#</a></h3>
<h4 id="141-三维点匹配">1.4.1. 三维点匹配<a hidden class="anchor" aria-hidden="true" href="#141-三维点匹配">#</a></h4>
<p>在三维点图空间中，通过最近邻搜索可以很容易地在两张图像的像素之间建立对应关系。为了将误差降至最低，我们通常会保留图像 $I^{1}$ 和 $I^{2}$ 之间的相互（双向）对应关系 $M_{1,2}$，也就是说，我们有：</p>
$$
\begin{array}{c}
\mathcal{M}_{1,2}=\left\{(i, j) | i=NN_{1}^{1,2}(j)\right. and \left.j=NN_{1}^{2,1}(i)\right\}\\
\text{where:  } NN_{k}^{n, m}(i)=\underset{j \in\{0, ..., W H\}}{arg min }\left\| X_{j}^{n, k}-X_{i}^{m, k}\right\|
\end{array}
$$<h4 id="142-恢复相机内参">1.4.2. 恢复相机内参<a hidden class="anchor" aria-hidden="true" href="#142-恢复相机内参">#</a></h4>
<p>恢复相机内参。根据定义，点图 $X^{1,1}$ 是在图像 $I^{1}$ 的坐标系中表示的。因此，通过求解一个简单的优化问题来估计相机内参是可行的。在这项工作中，我们假设主点大致位于中心位置，且像素为正方形，因此仅需估计焦距 $f_{1}^{*}$ ：</p>
$$
f_{1}^{*}=\underset{f_{1}}{\arg\min} \sum_{i = 0}^{W} \sum_{j = 0}^{H} C_{i, j}^{1,1}\left\| \left(i', j'\right)-f_{1} \frac{\left(X_{i, j, 0}^{1,1}, X_{i, j, 1}^{1,1}\right)}{X_{i, j, 2}^{1,1}}\right\|
$$<p>其中：</p>
$$
X_{i, j}^{1,1} = \left[\begin{array}{ccc}
  X_{i, j, 0}^{1,1} &
  X_{i, j, 1}^{1,1} &
  X_{i, j, 2}^{1,1}
\end{array}\right] ^\top
$$<p>且有 $i' = i - \frac{W}{2}$ 且 $j' = j - \frac{H}{2}$。</p>
<h4 id="143-相对位置姿估计">1.4.3. 相对位置姿估计<a hidden class="anchor" aria-hidden="true" href="#143-相对位置姿估计">#</a></h4>
<p>相对位姿估计可以通过多种方式实现。一种方法是如上述进行二维匹配并恢复内参，然后估计基础矩阵并恢复相对位姿。或者可以直接比较点图 $X^{1,1} \leftrightarrow X^{1,2}$ 以得到相对位姿 $P^{*}=[R^{*} | t^{*}]$ ：</p>
$$
R^{*}, t^{*}=\underset{\sigma, R, t}{\arg\min} \sum_{i} C_{i}^{1,1} C_{i}^{1,2}\left\| \sigma\left(R X_{i}^{1,1}+t\right)-X_{i}^{1,2}\right\| ^{2},
$$<p>这可以通过解析解得到。但是解析解对对噪声和外点十分敏感。可以考虑使用 RANSAC 求解。</p>
<h2 id="2-mast3r">2. MASt3R<a hidden class="anchor" aria-hidden="true" href="#2-mast3r">#</a></h2>
<p><img alt="MASt3R pipline" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/Images/MASt3RPipline.png"></p>
<p>MASt3R 和 DUSt3R 其实没有特别大的区别，事实上，MASt3R 和 DUSt3R 甚至没有太多新的代码。和 DUSt3R 相比，其主要增强了输入照片之间的特征匹配能力。具体而言，MASt3R 的 Encoder 和  Decoder 与 DUSt3R 是完全相同的，主要区别在于增加了一个预测头，用于预测出稠密的，逐像素的特征描述子 $F \in R^{W \times H \times d}$，其中 $d$ 是特征描述子的维度。</p>
<p>应为网络结构和三维结构预测头和 DUSt3R 是完全相同的，我们暂且不论，主要关注于 Local Feather Predicting Head 的训结构和训练。MASt3R 论文中提到，直接通过 DUSt3R 到的对应关系相当不精确，导致精度不够理想。其原因如下：</p>
<ol>
<li>回归本质上会受到噪声的影响</li>
<li>因为 DUSt3R 从未经过专门针对匹配的显式训练。</li>
</ol>
<h3 id="21-匹配头">2.1. 匹配头<a hidden class="anchor" aria-hidden="true" href="#21-匹配头">#</a></h3>
<p>匹配头。出于这些原因，我们提议添加第二个头，它输出两个维度为 $d$的密集特征图 $D^{1}$和 $D^{2}$， $D^{1},D^{2}\in \mathbb{R}^{H ×W ×d}$：</p>
$$
\begin{aligned}
D^{1} &= Head_{desc }^{1}\left(\left[H^{1}, H^{\prime 1}\right]\right)\\
D^{2} &= Head_{desc }^{2}\left(\left[H^{2}, H^{\prime 2}\right]\right)
\end{aligned}
$$<p>
（其中 $Head_{desc }^{1}$和 $Head_{desc }^{2}$ 为特征描述子预测头， $[H^{1}, H^{\prime 1}]$和 $[H^{2}, H^{\prime 2}]$为相应的输入组合，上式用于计算两个特征图 $D^{1}$和 $D^{2}$。我们将这个头实现为一个简单的两层多层感知器（MLP），其中穿插了非线性 GELU 激活函数。最后，我们将每个局部特征归一化为单位范数。</p>
<h3 id="22-匹配-loss">2.2. 匹配 loss<a hidden class="anchor" aria-hidden="true" href="#22-匹配-loss">#</a></h3>
<p>我们希望一张照片中的局部描述子最多与另一张照片中最多一个描述子相匹配，并且相互匹配的描述子描述了两张照片中同一个三维点。不妨假设我们有真值的匹配关系：</p>
$$
\hat {\mathcal M} = \{(i, j) \mid \hat X_{i}^{1,1} = \hat X_{i}^{1,2}\}
$$<p>可以通过 infoNCE loss 来寻找描述子之间的对应关系：</p>
$$
\mathcal{L}_{match }=-\sum_{(i, j) \in \hat{\mathcal{M}}} log \frac{s_{\tau}(i, j)}{\sum_{k \in \mathcal{P}^{1}} s_{\tau}(k, j)}+log \frac{s_{\tau}(i, j)}{\sum_{k \in \mathcal{P}^{2}} s_{\tau}(i, k)},
$$<p>其中：</p>
$$
s_{\tau}(i, j)=exp \left[-\tau D_{i}^{1 \top} D_{j}^{2}\right]
$$<p>在这里， $\mathcal P^{1}=\{i |(i, j) \in \hat{M}\}$和 $\mathcal P^{2}=\{j |(i, j) \in \hat{M}\}$分别表示每张图像中所考虑像素的子集，而 $\tau$是一个温度超参数。请注意，这个匹配目标本质上是一种交叉熵分类损失：与 DUSt3R 的 3D 点回归损失不同，只有当网络正确匹配到正确的像素时，它才会得到奖励。这极大地促使网络实现高精度匹配。最终的损失为：</p>
$$
\mathcal{L}_{total }=\mathcal{L}_{conf }+\beta \mathcal{L}_{match }
$$<h3 id="23-快速相互匹配-fast-reciprocal-matching">2.3. 快速相互匹配 (Fast reciprocal matching)<a hidden class="anchor" aria-hidden="true" href="#23-快速相互匹配-fast-reciprocal-matching">#</a></h3>
<p>给定两个预测的特征图 $D^{1}, D^{2} \in \mathbb{R}^{H×W×d}$，我们希望可以在两张图片之间提取出对应的像素匹配关系：</p>
$$
\mathcal{M}=\{(i, j) | j = NN_{2}(D_{i}^{1}) \text{ 且 } i = NN_{1}(D_{j}^{2})\}
$$<p>其中：</p>
$$
NN_{A}(D_{j}^{B}) = \underset{i}{\arg\min} \| D_{i}^{A} - D_{j}^{B}\|
$$<p>但是问题在于，我们目前面对的是一个稠密匹配问题。如果直接使用简单的暴力匹配，其时间复杂度将达到  $O(W^2H^2)$，着显然是无法接受的。当然，还可以用 k-d tree 一类的方法。但是事实上这类方法在特征的纬度非常高时时间复杂度也同样无法接受。因此，本文提出了一种名为 fast match 的方法，用于解决由于稠密匹配且特征维度过高导致的问题。</p>
<p>这个方法的核心思路是通过采样，降低每一次匹配过程的时间复杂度。这个过程是一个迭代过程，每一次迭代可以分为下面几步：</p>
<ol>
<li>我们现在特征图 $D^1$ 中进行随机采样，稀疏的得到 $k$ 个像素 $U^{0}=\{U_{n}^{0}\}_{n = 1}^{k}$。</li>
<li>通过 NN，在 $D^2$ 上找到 $U^{0}=\{U_{n}^{0}\}_{n = 1}^{k}$ 对应的 $k$ 个像素 $V^{1}=\{V_{n}^{1}\}_{n = 1}^{k}$。</li>
<li>将 $D^2$ 上找到的特征 $V^{1}=\{V_{n}^{1}\}_{n = 1}^{k}$ 重新通过 NN 在 $D^1$ 上寻找匹配点 $U^{1}=\{U_{n}^{1}\}_{n = 1}^{k}$。</li>
</ol>
$$
\begin{array}{c}
U^{t} \stackrel{}{\longrightarrow}\left[NN_{2}\left(D_{u}^{1}\right)\right]_{u \in U^{t}} = V^{t}\\
V^{t} \stackrel{}{\longrightarrow}\left[NN_{1}\left(D_{v}^{2}\right)\right]_{v \in V^{t}} = U^{t + 1}
\end{array}
$$<ol start="4">
<li>对比 $U^{0}=\{U_{n}^{0}\}_{n = 1}^{k}$ 和 $U^{1}=\{U_{n}^{1}\}_{n = 1}^{k}$。我们认为满足 $M_{k}^{t}=\{(U_{n}^{t}, V_{n}^{t}) | U_{n}^{t}=U_{n}^{t + 1}\}$ 的点已经完成匹配，会将这些点过滤掉。再进行下一轮循环。</li>
</ol>
<p><img alt="alt text" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/Images/FastMatching.png"></p>
<p><img alt="alt text" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/Images/MASt3R%20Matching.png"></p>
<p>如上图所示，实质上，对于初试选择的 $k$ 个点，很快的会辗转的匹配，直到完全匹配。</p>
<h3 id="24-从粗到细的匹配">2.4. 从粗到细的匹配<a hidden class="anchor" aria-hidden="true" href="#24-从粗到细的匹配">#</a></h3>
<p>事实上，自从 Transformer 提出的那一天开始，就因其长序列输入时的时间复杂度而被诟病。例如对于一个分辨率为 $(H, W)$ 的图像输入而言，如果将其划分为 $m$ 个 ViT 的图片序列，其时间复杂度为 $m^2$。因此 MASt3R 目前仅能处理最大为 512 像素的图像。事实上，高分辨率的图片往往首先需要降采样到 MASt3R 所能接受的最大水平，并将推理的结果上采样到原始分辨率。但是往往会导致性能损失和重建质量大幅下降。MASt3R 使用了从粗到细的匹配方法来解决这个问题。具体思路如下：</p>
<ol>
<li>对两张照片的降采样版本进行匹配。我们将子采样 $k$ 得到的粗匹配的对应关系记作 $M_{k}^{0}$</li>
<li>接下来在每幅全分辨率图像上独立生成一组有重叠的裁剪窗口 $W \in \mathbb{R}^{w×4}$。每个窗口都有 $512$ 像素，且重叠率为 $50\%$</li>
<li>枚举所有可能的窗口对 $(w_1, w_2) \in (W \times W)$。之后选择一个覆盖粗匹配 $M_{k}^{0}$ 的子匹配</li>
<li>具体来说，我们以贪心的方式逐个添加窗口对，直到覆盖90%的对应关系。最后，我们对每个窗口对独立进行匹配：</li>
</ol>
$$
\begin{aligned}
D^{\omega_{1}}, D^{\omega_{2}} &= \text{MASt3R}\left(I_{w_{1}}^{1}, I_{w_{2}}^{2}\right)\\
\mathcal{M}_{k}^{w_{1}, w_{2}} &= \text{Fast\_Reciprocal\_Match} \left(D^{w_{1}}, D^{w_{2}}\right)
\end{aligned}
$$<p><img alt="alt text" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/Images/MASt3R%20result.png"></p>
<p><img alt="alt text" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/Images/MASt3R%20result2.png"></p>
<h2 id="reference">Reference<a hidden class="anchor" aria-hidden="true" href="#reference">#</a></h2>
<p>@inproceedings{dust3r_cvpr24,
title={DUSt3R: Geometric 3D Vision Made Easy},
author={Shuzhe Wang and Vincent Leroy and Yohann Cabon and Boris Chidlovskii and Jerome Revaud},
booktitle = {CVPR},
year = {2024}
}</p>
<p>@misc{mast3r_arxiv24,
title={Grounding Image Matching in 3D with MASt3R},
author={Vincent Leroy and Yohann Cabon and Jerome Revaud},
year={2024},
eprint={2406.09756},
archivePrefix={arXiv},
primaryClass={cs.CV}
}</p>
<p>@inproceedings{croco,
title={{CroCo: Self-Supervised Pre-training for 3D Vision Tasks by Cross-View Completion}},
author={{Weinzaepfel, Philippe and Leroy, Vincent and Lucas, Thomas and Br'egier, Romain and Cabon, Yohann and Arora, Vaibhav and Antsfeld, Leonid and Chidlovskii, Boris and Csurka, Gabriela and Revaud J'er^ome}},
booktitle={{NeurIPS}},
year={2022}
}</p>
<p><a href="https://zhuanlan.zhihu.com/p/1890730282170181100">从DUSt3R 到 MASt3R</a></p>


  </div>

  <footer class="post-footer">
    <ul class="post-tags">
      <li><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/">深度学习</a></li>
      <li><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89/">计算机视觉</a></li>
      <li><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/%E4%B8%89%E7%BB%B4%E9%87%8D%E5%BB%BA/">三维重建</a></li>
    </ul>
<nav class="paginav">
  <a class="prev" href="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/">
    <span class="title">« Prev</span>
    <br>
    <span>From Transformer to VGGT</span>
  </a>
  <a class="next" href="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/02/homography/">
    <span class="title">Next »</span>
    <br>
    <span>homography</span>
  </a>
</nav>

  </footer>
</article>
    </main>
    
<footer class="footer">
        <span>&copy; 2025 <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/">WangJV Blog</a></span> · 

    <span>
        Powered by
        <a href="https://gohugo.io/" rel="noopener noreferrer" target="_blank">Hugo</a> &
        <a href="https://github.com/adityatelange/hugo-PaperMod/" rel="noopener" target="_blank">PaperMod</a>
    </span>
</footer>
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
        <path d="M12 6H0l6-6z" />
    </svg>
</a>

<script>
    let menu = document.getElementById('menu')
    if (menu) {
        menu.scrollLeft = localStorage.getItem("menu-scroll-position");
        menu.onscroll = function () {
            localStorage.setItem("menu-scroll-position", menu.scrollLeft);
        }
    }

    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener("click", function (e) {
            e.preventDefault();
            var id = this.getAttribute("href").substr(1);
            if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
                document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView({
                    behavior: "smooth"
                });
            } else {
                document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView();
            }
            if (id === "top") {
                history.replaceState(null, null, " ");
            } else {
                history.pushState(null, null, `#${id}`);
            }
        });
    });

</script>
<script>
    var mybutton = document.getElementById("top-link");
    window.onscroll = function () {
        if (document.body.scrollTop > 800 || document.documentElement.scrollTop > 800) {
            mybutton.style.visibility = "visible";
            mybutton.style.opacity = "1";
        } else {
            mybutton.style.visibility = "hidden";
            mybutton.style.opacity = "0";
        }
    };

</script>
<script>
    document.getElementById("theme-toggle").addEventListener("click", () => {
        if (document.body.className.includes("dark")) {
            document.body.classList.remove('dark');
            localStorage.setItem("pref-theme", 'light');
        } else {
            document.body.classList.add('dark');
            localStorage.setItem("pref-theme", 'dark');
        }
    })

</script>
<script>
    document.querySelectorAll('pre > code').forEach((codeblock) => {
        const container = codeblock.parentNode.parentNode;

        const copybutton = document.createElement('button');
        copybutton.classList.add('copy-code');
        copybutton.innerHTML = 'copy';

        function copyingDone() {
            copybutton.innerHTML = 'copied!';
            setTimeout(() => {
                copybutton.innerHTML = 'copy';
            }, 2000);
        }

        copybutton.addEventListener('click', (cb) => {
            if ('clipboard' in navigator) {
                navigator.clipboard.writeText(codeblock.textContent);
                copyingDone();
                return;
            }

            const range = document.createRange();
            range.selectNodeContents(codeblock);
            const selection = window.getSelection();
            selection.removeAllRanges();
            selection.addRange(range);
            try {
                document.execCommand('copy');
                copyingDone();
            } catch (e) { };
            selection.removeRange(range);
        });

        if (container.classList.contains("highlight")) {
            container.appendChild(copybutton);
        } else if (container.parentNode.firstChild == container) {
            
        } else if (codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
            
            codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(copybutton);
        } else {
            
            codeblock.parentNode.appendChild(copybutton);
        }
    });
</script>
</body>

</html>
